<?php

namespace Cephalopod;

use SplQueue;
use Exception;

class HttpTube implements Tube
{
    private SplQueue $queue;

    private string $buffer = '';

    public function __construct()
    {
        $this->queue = new SplQueue();
    }

    public function dequeue(): string
    {
        $result = '';
        while (!$this->queue->isEmpty()) {
            $this->buffer .= $this->queue->dequeue();
        }
        if ('' == $this->buffer) {
            return '';
        }
        do {
            list($firstLine, $headers, $body, $unknowData, $isPertty) = Http::separate($this->buffer);
            if ($isPertty) {
                $result .= $this->getInsideData($body, $headers);
                $this->buffer = $unknowData;
                if ('' === $this->buffer) {
                    return $result;
                }
            }
        } while ($isPertty);
        return $result;
    }

    public function enqueue(string $binraryData)
    {
        if ('' == $binraryData) {
            return;
        }
        $this->queue->enqueue($binraryData);
    }

    public static function buildUploadPostData(string $data, string $host, int $port)
    {
        $block = "Content-Disposition: form-data; name=\"file\"; filename=\"document.dat\"\r\n"
               . "Content-Type: application/octet-stream\r\n"
               . "\r\n"
               . "{$data}";

        $dataLength = strlen($block);
        do {
            $boundary = static::getBoundary($dataLength);
        } while (false !== strpos($data, $boundary));

        $body = "--{$boundary}\r\n{$block}\r\n--{$boundary}--\r\n";
        $firstLine = "POST /upload HTTP/1.1";
        $headers = [
            ['Host',           "$host:$port"],
            ['Connection',     'keep-alive'],
            ['Content-Length', strlen($body)],
            ['Origin',         "http://$host:$port"],
            ['Content-Type',   "multipart/form-data; boundary=$boundary"],
            ['Referer',        "http://$host:$port/"],
        ];
        return Http::buildHttpMessage($firstLine, $headers, $body);
    }

    public static function buildResponseData(string $body): string
    {
        $firstLine = "HTTP/1.1 200 OK";
        $headers = [
            ["Connection",       "keep-alive"],
            ["Content-Length",   strlen($body)],
            ["Content-Type",     "application/octet-stream"],
        ];
        return Http::buildHttpMessage($firstLine, $headers, $body);
    }

    private static function getBoundary(int $dataSize): string
    {
        if ($dataSize < 2) {
            return uniqid();
        }
        // The probability of repetition
        $prepet = 0.001;
        $randomStringSize = 0;
        $baseChars = '0123456789abcdefghijklnmopqrstuvwxyzABCDEFGHIJKLNMOPQRSTUVWXYZ';
        $baseCharsNumber = strlen($baseChars);
        do {
            $randomStringSize++;
            $p = 1 - (1 - (1 / $baseCharsNumber) ** $randomStringSize) ** ($dataSize - $randomStringSize + 1);
        } while ($p >= $prepet);
        $result = '';
        for ($i = 0; $i < $randomStringSize; $i++) {
            $result .= substr($baseChars, mt_rand(0, $baseCharsNumber - 1), 1);
        }
        return $result;
    }

    public function getInsideData(string $body, array $headers): string
    {
        $isMultipartFormData = false;
        $boundary = '';
        foreach ($headers as $info) {
            list($field, $val) = $info;
            if ('content-type' == strtolower($field)) {
                if (0 === strpos(strtolower($val), 'multipart/form-data;')) {
                    $isMultipartFormData = true;
                    if (false !== ($offset = strpos($val, 'boundary='))) {
                        $boundary = substr($val, $offset + strlen('boundary='));
                    }
                }
                break;
            }
        }
        if (!$isMultipartFormData) {
            return $body;
        }
        if ('' == $boundary) {
            throw new Exception('Error, boundary is empty');
        }
        foreach (explode("--$boundary", $body) as $item) {
            $contentLength = strlen($item) - strpos($item, Http::HTTP_EOL . Http::HTTP_EOL) - 2 * strlen(Http::HTTP_EOL);
            list($firstLine, $itemHeaders, $itemBody, $unknowData, $isPertty) = Http::separate("HTTP/1.1 200 OK\r\nContent-Length: {$contentLength}{$item}");
            foreach ($itemHeaders as $info) {
                list($field, $val) = $info;
                if (false !== strpos($val, "filename=\"document.dat\"")) {
                    $itemBodyLength = strlen($itemBody);
                    if ($itemBodyLength < 2) {
                        throw new Exception('Error, no data in multipart/form-data');
                    }
                    return substr($itemBody, 0, $itemBodyLength - 2); // \r\n length is 2, so -2, data is end of \r\n
                }
            }
        }
        return '';
    }
}
